package com.copm.ifm.base.service;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.service.IService;
import com.copm.ifm.base.basic.constant.DbConstants;
import com.copm.ifm.base.basic.constant.NumberConstants;
import com.copm.ifm.base.basic.exception.ServiceException;
import com.copm.ifm.base.basic.pojo.BaseTreePO;
import com.copm.ifm.base.basic.util.ObjectUtils;
import com.copm.ifm.base.service.util.TreeUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

import static com.copm.ifm.base.basic.constant.DbConstants.DEFAULT_YES;

/**
 * 树状图相关接口
 *
 * @author zzf
 * @date 2021/1/11 13:56
 */
public interface TreeService<T extends BaseTreePO> extends IService<T> {

    /**
     * 添加树状对象
     *
     * @param entity 树状对象
     */
    @Transactional(rollbackFor = Exception.class)
    default void addTree(T entity) {

        if (StringUtils.isBlank(entity.getParentPath())) {
            entity.setParentPath(null);
        } else {
            if (entity.getParentPath().length() % TreeUtil.DEFAULT_PATH_LENGTH > 0) {
                throw new ServiceException("父路径取值错误！");
            }
        }
        //前置任务
        beforeTreeAddProcessor(entity);

        //添加
        entity.setPath(this.getNewChildPath(entity, entity.getParentPath()));
        entity.setLevel(TreeUtil.getLevel(entity.getPath()));
        entity.setEnabled(DEFAULT_YES);

        this.save(entity);

        //后置任务
        afterTreeAddProcessor(entity);

        //判断并修改父级 是否叶子节点
        if (StrUtil.isNotEmpty(entity.getParentPath())) {
            modifyParentLeafNodeToFalse(entity);
        }
    }

    /**
     * 移动节点
     *
     * <p>
     * 1.更新原父节点（注意无原父节点情况）
     * 2.更新新父节点（注意无新父节点情况）
     * 3.遍历所有子节点，批量更新
     * 4.更新自己（前面的更新需要用到 entity 的 path 和 parentPath）
     *
     * @param movedNodeId     需要移动的节点id
     * @param newParentNodeId 新父级节点id
     */
    @Transactional(rollbackFor = Exception.class)
    default void moveNode(Integer movedNodeId, Integer newParentNodeId) {

        if (ObjectUtil.isEmpty(movedNodeId)) {
            throw new ServiceException("movedNodeId不能为空。");
        }

        // 记录需要修改的对象
        List<T> batchUpdateList = new ArrayList<>();

        T entity = getBaseMapper().selectById(movedNodeId);

        // 1.更新原父节点的叶子节点（注意无原父节点情况）
        this.checkAndModifyParentLeafNodeToYes(entity);

        String newParentPath;
        if (ObjectUtils.isNull(newParentNodeId)) {
            newParentPath = "";
        } else {
            T parentNode = getBaseMapper().selectById(newParentNodeId);
            newParentPath = parentNode.getPath();

            // 2.更新新父节点
            if (parentNode.getLeafNode()) {
                parentNode.setLeafNode(false);
                getBaseMapper().updateById(parentNode);
            }
        }

        String oldChildPath = entity.getPath();
        String newChildPath = this.getNewChildPath(entity, newParentPath);

        // 3.遍历所有子节点，批量更新
        LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<T>()
                // 这里要用likeRight，将其所有子节点全部修改
                .likeRight(T::getParentPath, entity.getPath());

        List<T> childrenList = this.getBaseMapper().selectList(this.doAppend(wrapper, entity));
        if (CollectionUtil.isNotEmpty(childrenList)) {
            for (T s : childrenList) {
                s.setParentPath(s.getParentPath().replaceFirst(oldChildPath, newChildPath));
                s.setPath(s.getPath().replaceFirst(oldChildPath, newChildPath));
                s.setLevel(TreeUtil.getLevel(s.getPath()));
            }
            batchUpdateList.addAll(childrenList);
        }

        // 4.更新自己
        entity.setParentPath(newParentPath);
        entity.setPath(newChildPath);
        entity.setLevel(TreeUtil.getLevel(newChildPath));
        batchUpdateList.add(entity);

        this.updateBatchById(batchUpdateList);
    }

    /**
     * 删除节点
     * 1.判断存在未删除的孩子节点，不允许删除
     * 2.更新自己的 deleted 字段
     * 3.判断是否更新父节点
     *
     * @param id 需要删除的节点id
     */
    default void deleteNode(Integer id) {

        T entity = getBaseMapper().selectById(id);
        if (!entity.getLeafNode()) {
            throw new ServiceException("该节点下存在未删除的孩子节点，不允许删除。");
        }

        // 2.更新自己的 deleted 字段
        entity.setDeleted(DEFAULT_YES);
        this.getBaseMapper().updateById(entity);

        // 3.判断是否更新父节点
        this.checkAndModifyParentLeafNodeToYes(entity);

    }

    /**
     * 获取 parentPath下的 最大的子节点路径
     *
     * @param entity     树状对象，提供其他查询条件，
     * @param parentPath 指定父路径，可能和 entity.getParentPath 不一致
     * @return 当前最大的子节点路径
     */
    default String getCurrentMaximumChildPath(T entity, String parentPath) {

        LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<T>()
                .orderByDesc(T::getPath)
                .last("limit 1");

        if (StrUtil.isEmpty(parentPath)) {
            wrapper.isNull(T::getParentPath);
        } else {
            wrapper.eq(T::getParentPath, parentPath);
        }

        T maxPathChild = this.getBaseMapper().selectOne(this.doAppend(wrapper, entity));
        return ObjectUtil.isEmpty(maxPathChild) ? null : maxPathChild.getPath();
    }

    /**
     * 获取 parentPath 下的新子路径
     *
     * @param entity     提供其他查询条件
     * @param parentPath 父节点路径
     * @return parentPath 下的最新子路径
     */
    default String getNewChildPath(T entity, String parentPath) {

        String newChildPath;
        String currentMaximumChildPath = this.getCurrentMaximumChildPath(entity, parentPath);
        if (StrUtil.isEmpty(currentMaximumChildPath)) {
            newChildPath = TreeUtil.getFirstChildPath(parentPath);
        } else {
            newChildPath = TreeUtil.getNextChildPath(currentMaximumChildPath);
        }
        return newChildPath;
    }

    /**
     * 查询父节点
     *
     * @param entity 节点对象
     * @return 父节点
     */
    default T doSelectParent(T entity) {
        // default
        LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isBlank(entity.getParentPath())) {
            throw new ServiceException("该节点为一级节点，不存在父节点！");
        } else {
            wrapper.eq(T::getPath, entity.getParentPath());
        }

        List<T> list = this.getBaseMapper().selectList(this.doAppend(wrapper, entity));

        if (CollectionUtil.isEmpty(list)) {
            throw new ServiceException("未找到父节点或数据传输错误，请联系管理员。");
        }

        if (list.size() > 1) {
            throw new ServiceException("查询到的父节点个数大于1，数据异常，请联系管理员。");
        }
        return list.get(0);
    }

    /**
     * 拼接 wrapper 条件后查询返回查询集合
     *
     * @param wrapper 默认 wrapper
     * @param entity  提供其他查询条件
     * @return 返回查询集合
     */
    default LambdaQueryWrapper<T> doAppend(LambdaQueryWrapper<T> wrapper, T entity) {
        wrapper.eq(T::getDeleted, DbConstants.DEFAULT_NO);
        List<SFunction<T, ?>> selectConditionFunctions = this.appendSelectConditionFunctions();
        if (ObjectUtils.isNotNull(selectConditionFunctions)) {
            selectConditionFunctions.forEach(
                    tsFunction -> wrapper.eq(tsFunction, tsFunction.apply(entity)));
        }
        return wrapper;
    }

    /**
     * 添加查询参数
     * <p>
     * 这个是为了适应不同实体类的树状结构条件：
     * 如果一个实体类只会有一个树状结构，则不需要添加条件，反之则需要传相关条件来确定是那颗树
     * <p>
     * 举个例子：
     * 假如我们的菜单是针对用户的，即每个用户都有一个菜单树，都保存在SysMenu表中
     * 那么我们在编辑一棵树时就需要传相关用户id来唯一确定一棵树。
     * <p>
     * 换句话说，在只有一棵树的情况下，path和parentPath都是唯一的，
     * 当存在一个或多个维度(上例中的用户)来区分树时，path和parentPath就不是唯一的了。
     * <p>
     * 所以这里需要传入对应的条件使得根据该条件能够查询到唯一的数据。
     * 按上述例子来说，就应该传入Arrays.asList(SysMenu::getUserId);
     *
     * @return 用于Wrapper中的参数方法集合
     */
    List<SFunction<T, ?>> appendSelectConditionFunctions();

    /**
     * 修改父节点的是否叶子节点为 false
     *
     * @param entity 树状对象
     */
    default void modifyParentLeafNodeToFalse(T entity) {
        T parent = this.doSelectParent(entity);
        parent.setLeafNode(false);
        this.getBaseMapper().updateById(parent);
    }

    /**
     * 判断是否修改父节点的是否叶子节点为 true
     *
     * @param entity 树状对象
     */
    default void checkAndModifyParentLeafNodeToYes(T entity) {

        if (StrUtil.isNotEmpty(entity.getParentPath())) {
            T parent = this.doSelectParent(entity);
            if (ObjectUtil.isNotEmpty(parent)) {
                LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<T>()
                        .eq(T::getParentPath, entity.getParentPath());
                Integer childCount = this.getBaseMapper().selectCount(this.doAppend(wrapper, entity));
                parent.setLeafNode(childCount.equals(1));
                this.getBaseMapper().updateById(parent);
            }
        }
    }

    /**
     * 前置任务，比如名字判重等工作
     *
     * @param entity 树状对象
     */
    default void beforeTreeAddProcessor(T entity) {

    }

    /**
     * 后置任务
     *
     * @param entity 树状对象
     */
    default void afterTreeAddProcessor(T entity) {

    }

}
